random_bouncing = {
const height = 400;
const margin = 50;
const svg = d3.create("svg").attr("width", width).attr("height", height);
svg
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height)
.attr("fill", "#002");
const fakeData = d3.range(maximumDots).map((d) => {
return { x: Math.random(), y: Math.random(), z: Math.random() };
});
const xScale = d3
.scaleLinear()
.domain([0, 1])
.range([margin, width - margin]);
const yScale = d3
.scaleLinear()
.domain([0, 1])
.range([height - margin, margin]);
//every data point will be passed to .enter() since we have no existing SVG elements
svg
.selectAll(".fakeDots")
.data(fakeData)
.enter()
.append("circle")
.attr("cx", (d) => xScale(d.x))
.attr("cy", (d) => yScale(d.y))
.attr("fill", (d) => d3.interpolateMagma(d.z))
.attr("stroke", "white")
.attr("stroke-width", 0)
.attr("r", 5)
.attr("class", "fakeDots");
//create svg group to hold button and button label
const g = svg.append("g");
//add button background rectangle
g.append("rect")
.attr("fill", "orange")
.attr("width", 50)
.attr("height", 20)
.attr("x", width - 60)
.attr("y", 10)
//let this button listen for a click
.on("click", (event, datum) => {
//when the button is clicked...
//generate a sequence of numbers of random length
//generate fake data for each item in the sequence
const newFakeData = d3.range(maximumDots * Math.random()).map((d) => {
return { x: Math.random(), y: Math.random(), z: Math.random() };
});
//look for existing dots and compare them with the new data
//sometimes we will have more data in the array, sometimes less
//so we need to add elements and remove elements, but also update existing dots to their new values
const match = svg.selectAll(".fakeDots").data(newFakeData);
//for new data without a matching dot we use .enter()
//this code is almost identical to how we built the first generation of circles
//the new circles are drawn with 0 radius, and then grow
match
.enter()
.append("circle")
.attr("cx", (d) => xScale(d.x))
.attr("cy", (d) => yScale(d.y))
.attr("r", 0)
.attr("fill", (d) => d3.interpolateMagma(d.z))
.attr("stroke", "white")
.attr("stroke-width", 0)
.attr("class", "fakeDots")
.transition()
.duration(1000)
.delay((d) => Math.random() * 1000)
.ease(d3.easeBounce)
.attr("r", (d) => 5);
//for SVG elements without data to support it, we use .exit()
//these circles will shrink to 0 radius and then get removed from SVG
match
.exit()
.transition()
.duration(1000)
.delay((d) => Math.random() * 1000)
.ease(d3.easeExpOut)
.attr("r", 0)
.remove();
//for data that had matching SVG elements, we just update the position and color
//but we don't need to call .update()... since it's the default behavior
match
.transition()
.duration(1000)
.delay((d) => Math.random() * 1000)
.ease(d3.easeBackOut.overshoot(1.7))
.attr("fill", (d) => d3.interpolateMagma(d.z))
.attr("cx", (d) => xScale(d.x))
.attr("cy", (d) => yScale(d.y));
});
//label for the button
g.append("text")
.attr("fill", "white")
.attr("x", width - 58)
.attr("y", 22)
.text("Shuffle!")
.style("font-family", "courier")
.style("font-size", 10)
//so the text doesn't get in the way of our button seeing the mouse click...
.attr("pointer-events", "none");
return svg.node();
}