import * as d3 from 'd3'; // d3js module for module05: reduce bubble chart export default () => { // defaults const options = { width: 912, height: 350, radius: 5, numberOfNodes: 500, setData: () => {} // eslint-disable-line no-empty-function }; // start const bubbleChart = selection => { const svg = selection.select('svg'); // shuffle array const shuffle = array => { let currentIndex = array.length; let temporaryValue; let randomIndex; // While there remain elements to shuffle... while (currentIndex !== 0) { // Pick a remaining element... randomIndex = Math.floor(Math.random() * currentIndex); currentIndex -= 1; // And swap it with the current element. temporaryValue = array[currentIndex]; array[currentIndex] = array[randomIndex]; array[randomIndex] = temporaryValue; } return array; }; // select `numberOfNodes` random const circles = svg.selectAll('.circle'); const baseArr = []; for (let i = 0; i < circles.size(); i += 1) baseArr.push(i); // segmenting circles const shuffledArr = shuffle(baseArr); const falling = shuffledArr.slice(0, options.numberOfNodes); const keeping = shuffledArr.slice(options.numberOfNodes, shuffledArr.length); const circlesFalling = circles.filter(d => falling.includes(d.index)); const circlesKeeping = circles.filter(d => keeping.includes(d.index)); const factor = circlesKeeping.size() === 200 ? 0.3 : 0.07; // reorder remaining circles at the bottom const reorderRemaining = () => { const ticked = () => { circlesKeeping .attr('cx', d => (d.x = Math.max(options.radius, Math.min(options.width - options.radius, d.x)))) .attr('cy', d => (d.y = Math.min(options.height - options.radius, d.y))); }; // .. using a force simulation const simulation = d3.forceSimulation() .force('y', d3.forceY(() => (options.height)) .strength(d => ((options.height - d.y) / (options.height) * factor))) .velocityDecay(0.6) .alphaDecay(0.08) .force('collide', d3.forceCollide(options.radius * 2) .strength(0.5) ); // add timer const endTime = 1500; const transitionTimer = d3.timer(elapsed => { const dt = elapsed / endTime; if (dt >= 1.0) { transitionTimer.stop(); simulation.stop(); options.setData({ continue: true }); } }); // move circles simulation.nodes(circlesKeeping.data()) .on('tick', ticked); }; // change color of falling circles circlesFalling .transition() .ease(d3.easeSin) .duration(1000) .attr('fill', '#D0021B'); // change y coordinate of falling circles // after they have been fallen down, remove them circlesFalling .transition() .delay(1000) .duration(d => ((options.height - d.y) * 5)) .ease(d3.easeCircleIn) .attr('cy', options.height * 1.25) .remove(); window.setTimeout(() => (reorderRemaining()), 1500); }; // "setter" bubbleChart.options = input => { Object.assign(options, input); return bubbleChart; }; return bubbleChart; };