chart = (radius = 10, padding = 2) => {
let width = window.innerWidth;
let height = 0.5 * width;
let svg = d3.create("svg").attr("width", width).attr("height", height).style("background", "whitesmoke");
let container = svg.append("g").attr("class", "board").attr("transform", `translate(${[width/2, height/2]})`);
let line = d3.linkHorizontal()
.x(d => d.x)
.y(d => d.y);
let lineData = { source: { x: -0.4 * width, y: -0.36 * height }, target: { x: 0.43 * width, y: 0.36 * height } };
let linePoints = [{x: -0.4 * width, y: -0.36 * height},
{x: 0.4 * width, y: -0.36 * height},
{x: 0.4 * width + 20, y: -0.36 * height - 20},
{x: 0.4 * width, y: -0.36 * height - 40},
{x: -0.4 * width, y: -0.36 * height - 40}
];
let lc = d3.line()
.curve(d3.curveMonotoneY)
.x(d => d.x)
.y(d => d.y);
let path = container
.append("path")
.attr("d", lc(linePoints))
.style("stroke", "#FF7F0F")
.style("fill", "none");
let path1 = container
.append("path")
.attr("d", line(lineData))
.style("stroke", "#FF7F0F")
.style("fill", "none");
let pos = lineup(path, radius, padding);
const updateQueued = (data) => {
let circles = container.selectAll("circle.queued").data(data, d => d.id);
circles
.enter()
.append("circle")
.attr("class", "queued")
.attr("id", d => d.id)
.attr("r", 10)
.style("stroke", d3.rgb("steelblue").darker())
.style("fill", "steelblue")
.style("opacity", 0.1)
.attr("transform", `translate(${[-width/2, linePoints[linePoints.length - 1].y]})`)
.transition()
.duration(500)
.style("opacity", 1)
.attr("transform", `translate(${[linePoints[linePoints.length - 1].x, linePoints[linePoints.length - 1].y]})`)
.transition()
.duration(1500)
.ease(d3.easeSinInOut)
.attrTween("transform", (d,i) => {
const s = d3.interpolate(d.startPos * path.node().getTotalLength(), d.endPos * path.node().getTotalLength());
return function(t) {
let p = path.node().getPointAtLength(s(t));
return 'translate(' + p.x + ',' + p.y + ')';
};
});
circles
.transition()
.duration(1500)
.delay((d,i) => 50 * i)
.attrTween("transform", function(d,i) {
const s = d3.interpolate(d.startPos * path.node().getTotalLength(), d.endPos * path.node().getTotalLength());
return function(t) {
let p = path.node().getPointAtLength(s(t));
return 'translate(' + p.x + ',' + p.y + ')';
};
});
circles
.exit()
.transition()
.duration(1000)
.attrTween("transform", (d,i) => {
const s = d3.interpolate(0 * path1.node().getTotalLength(), 1 * path1.node().getTotalLength());
return function(t) {
let p = path1.node().getPointAtLength(s(t));
return 'translate(' + p.x + ',' + p.y + ')';
};
})
.transition()
.duration(500)
.style("fill", "white")
.transition()
.duration(500)
.style("opacity", 0)
.attr("transform", `translate(${[width/2,lineData.target.y]})`)
.remove();
};
let data = [];
let t = 0;
d3.select("#startButton").on("click", () => {
let ticker = d3.interval(elapsed => {
console.log(elapsed, t, data.length)
data.forEach((d,i) => {d.startPos = d.endPos;});
data.push({id: Math.random(), startPos: 1, endPos: pos[data.length].t});
if (t % 3 === 0) {
data.forEach((d,i) => {d.endPos = i > 0 ? data[i-1].startPos : 0});
data.shift();
}
updateQueued(data);
t += 1;
if (elapsed > 40000) ticker.stop();
}, 2600);
});
return svg.node();
}