| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116 |
- 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;
- };
|